﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using System.Diagnostics;
using System.Drawing;

namespace DirectX_CS
{
	public class EquilibriumWave3d 
	{
		protected Vector2 centerPosition = new Vector2(0, 0);
		[PGBrowsable]
		public Vector2 CenterPosition
		{
			get { return centerPosition; }
			set { centerPosition = value; }
		}

		protected static int NumberOfWavesInMemory = 0;
		protected int waveNumber = -1;
		[PGBrowsable]
		public int WaveNumber
		{
			get { return waveNumber; }
		}
		[PGBrowsable]
		public float X
		{
			get { return centerPosition.X; }
			set { centerPosition.X = value; }
		}
		[PGBrowsable]
		public float Y
		{
			get { return centerPosition.Y; }
			set { centerPosition.Y = value; }
		}

		public EquilibriumWave3d(float deltaTime)
		{
			DeltaTime = deltaTime;
			NumberOfWavesInMemory++;
			this.waveNumber = NumberOfWavesInMemory;
		}

		private float tOffset = 0;
		protected float cx = 1f, cy = 1f;
        protected float cz = 1f;

		protected bool useZAxis = true;
		[PGBrowsable]
		public bool UseZAxis
		{
		  get { return useZAxis; }
		  set { useZAxis = value; }
		}
		protected bool useXAxis = false;
		[PGBrowsable]
		public bool UseXAxis
		{
		  get { return useXAxis; }
		  set { useXAxis = value; }
		}
		protected bool useYAxis = false;
		[PGBrowsable]
		public bool UseYAxis
		{
		  get { return useYAxis; }
		  set { useYAxis = value; }
		}
		[PGBrowsable]
		public float Cz
		{
			get { return cz; }
			set { cz = value; }
		}

		[PGBrowsable]
		public float Cy
		{
			get { return cy; }
			set { cy = value; }
		}
		[PGBrowsable]
		public float Cx
		{
			get { return cx; }
			set { cx = value; }
		}
        //protected float fx = 1f, fy = 1f, fz = 1f;

		public float sqrt2PI = (float)Math.Sqrt(2 * Math.PI);
		[PGBrowsable]
		public float T0	  //also a start time
		{
			get { return tOffset; }
			set { tOffset = value; }
		}
		[PGBrowsable]
		public float MinLifeTime = 0;
		private float mMaxLifeTime = 10;
		[PGBrowsable]
		public float MaxLifeTime
		{
			get { return mMaxLifeTime; }
			set { mMaxLifeTime = value; }
		}

		public bool PastLifeTime(float time)
		{
			return time - tOffset > mMaxLifeTime; 
		}
		public float TFraction(float time)
		{
            return (time - tOffset) / mMaxLifeTime;
		}
		private static float sin(double t)
		{
			return (float)Math.Sin(t);
		}
		private static float cos(double t)
		{
			return (float)Math.Cos(t);
		}
		private static float max(float l, float r)
		{
			return l > r ? l : r;
		}
		private static float min(float l, float r)
		{
			return l < r ? l : r;
		}
		protected bool outOfBoundsZero = false;
		[PGBrowsable]
		public bool OutOfBoundsZero
		{
			get { return outOfBoundsZero; }
			set { outOfBoundsZero = value; }
		}
		[PGBrowsable]
		public string Name
		{
			get
			{
				return "#" + waveNumber + "\"" + WaveType.ToString() + "\", "  ;
			}
		}
		float scaleN = 100f;
		float scaleAmp = 10f;		
		/// <summary>
		/// Gradient ...
		/// </summary>
		/// <param name="x">x world coordinate units</param>
		/// <param name="y">y coordinate in world units</param>
		/// <param name="y">z coordinate in world units, not currently used, but I will in the future.</param>
		/// <returns>Gradient of the surface wave function.</returns>
		public Vector3 Gradient(Vector3 pos)  
		{
			float x = pos.X, y = pos.Y, z = pos.Z;
			if (mWaveType == WaveEquationType.NormalCurve)
			{
				float scaleNSq = scaleN * scaleN;
				float varSq = normalVariance * normalVariance;
				
				float gx, gy, gz;
				float rlensq = x * x + y * y;
				float rlen = (float)Math.Sqrt(rlensq);
				float r = (float)(rho - rlen); 
				float rsq = (float)r * r;
				float denom =(float)Math.Sqrt(2d*Math.PI) * scaleNSq * Math.Abs(normalVariance) * varSq * rlen;
				float exp = (float)Math.Exp(-rsq/(2d * scaleNSq * varSq));
				float n1 = (cz * exp * scaleAmp * r);
				gx =  n1 * x / denom;
				gy = n1 * y / denom;
				gz = 0f;   //dont understand why Mathematica said zero but i know its actually 1
				Vector3 v = new Vector3(gx, gy, gz); //v.Normalize(); 
				return v;
			}
			else if (mWaveType == WaveEquationType.Sin)
			{
				float rlen = (float)Math.Sqrt(x * x + y * y);
				float r = (float)rho - rlen;
				float n1 = (float)(-scaleAmp * frequency * Math.Cos(frequency * r / FixedWaveCount));
				float denom = FixedWaveCount * rlen;
				float gx = x * n1/denom ;
				float gy = y * n1/denom;
				float gz = 0f;
				Vector3 v = new Vector3(gx, gy, gz); //v.Normalize();
				return v;
			}
			else //(mWaveType == WaveEquationType.Cos)
			{
				//same thing except exchange cos with sin
				float rlen = (float)Math.Sqrt(x * x + y * y);
				float r = (float)rho - (float)Math.Sqrt(x * x + y * y);
				float n1 = (float)(-scaleAmp * frequency * Math.Sin(frequency * r / FixedWaveCount));
				float denom = FixedWaveCount * rlen;
				float gx = x * n1 / denom;
				float gy = y * n1 / denom;
				float gz = 0f;
				Vector3 v = new Vector3(gx, gy, gz);// v.Normalize();
				return v;
			}
		}

		public SVector displacement(Vector2 rendPoint)   //need to change to a scalar valued function not a vector field
		{
			float time = mLastTime;
			if (WaveType ==WaveEquationType.WaterEffect)
			{

				float rendDist = rendPoint.Length() + 3.14f;
			    double dPLen = rendDist + time;
			    float d = cz * sin(dPLen * frequency);
				return new SVector(new Vector3(0, 0, d), Color.Yellow);
			} else 
			{
				//NOT WATER EFFECT...
				Vector2 renderPosCentered = rendPoint - new Vector2(centerPosition.X, centerPosition.Y);   //
				float rendPosCenteredLen = renderPosCentered.Length();   //

				//lengthOfEffect = SimpleDecay(time,  Cz * 20, 0, MaxLifeTime ); //amplitude lower height-wise than is wide

				float rMinLen = min((float)0, max(0, (float)(0 - lengthOfEffect))); //
				float rMaxLen = 0; //
				bool inBounds = rendPosCenteredLen >= rMinLen && rendPosCenteredLen <= rMaxLen;// (Rend.Length - rhoLen) > lengthOfEffect;


				//Color colorX = Color.White, cy = Color.Red, cz = Color.Yellow;
				float dX = 0;
				float dY = 0;
				float dZ = 0;

				//change coloring for inbounds
				Color theColor = Color.White;
			    theColor = inBounds ? Color.Yellow : Color.Red;

				if (inBounds || (!inBounds && !OutOfBoundsZero))
				{
					if (UseZAxis)		  //this has changed... should not have, but I havent prevented it yet, more than one active at a time.. need a scalar function so ..
					{
						dZ = DZ(Cz, time, renderPosCentered, ref theColor);
					}
					else if (useXAxis)
					{
						dX = DX(Cx, time, renderPosCentered, ref theColor);
					}
					else if (useYAxis)
					{
						dY = DY(Cy, time, renderPosCentered, ref theColor);
					}	
				}


				return new SVector(new Vector3(dX, dY, dZ), theColor);

			}

		}

		private float deltaTime = .003f;
		[PGBrowsable]
		public float DeltaTime
		{
			get { return deltaTime; }
			set { deltaTime = value; }
		}

		public void DoMotion()
		{
			float dt = deltaTime;
			if (this.TimerEnabled)
			{
				if (mLastTime == 0)
					rho = rho0;
				else
					rho += rhoSpeed * dt;
				//now set time
				mLastTime += dt;
			}
		}

		
		private float SimpleDecay(float time, float startHeight, float EndHeight, float timeToEnd)
		{
			float m = (startHeight - EndHeight) / timeToEnd;
			float h = startHeight - m * time;//rho / 4f;	// should be  a function of length of one side of the "rendering square" or of wave counts
			return h;
		}
		private float h = 10f;
 
		protected float rho0 = 0;
		[PGBrowsable]
		public float Rho0
		{
			get { return rho0; }
			set { rho0 = value; }
		}

        protected float rhoSpeed = 15;
		[PGBrowsable]
		public float RhoSpeed
        {
            get { return rhoSpeed; }
            set { 
				rhoSpeed = value;
				//set ro0 so that the position can be computed as a function of time
				//Rho0 = Rho;
				//tOffset = mLastTime;
			}
        }

        private float nSin(float t, float waveLength)
        {
           return sin(t  /  waveLength  * HalfPi);
        }
        public const float HalfPi = (float)(Math.PI / 2d);

        float mFixedWaveCount = 1.0f;
		[PGBrowsable]
        public float FixedWaveCount
        {
			get { return mFixedWaveCount; }
            set 
			{ 
				if (value != 0)
					mFixedWaveCount = value; 
			}
        }
        float mLastTime = 0;

		public void SetTime(float newTime)
		{
			//if (mTimerEnabled)
			//{
			//    mLastTime = newTime;
			//} //else don't set it

			mLastTime = newTime;
		}
		[PGBrowsable]
		public float Time
        {
            get { return mLastTime; }
            set { mLastTime = value; }
        }

		float rho = 0;
		[PGBrowsable]
		public float Rho
		{
			get { return rho; }
			set { rho = value; }
		}

		float lengthOfEffect = (float)10;//(2*Math.PI);
		[PGBrowsable]
		public float LengthOfEffect
		{
			get { return lengthOfEffect; }
			set { lengthOfEffect = value; }
		}

		protected WaveEquationType mWaveType = WaveEquationType.Sin;
		[PGBrowsable]
		public WaveEquationType WaveType
		{
			get { return mWaveType; }
			set { mWaveType = value; }
		}

		float normalVariance = 10f;// should be lower when breaking
		[PGBrowsable]
		public float NormalVariance
		{
			get { return normalVariance; }
			set { normalVariance = value; }
		}
		protected const float TwoPi = (float)(2d * Math.PI);

		private float DX(float amp, float time, Vector2 renderPosCentered, ref Color clr)
		{
			return DZ(amp, time, renderPosCentered, ref clr);
		}
		private float DY(float amp, float time, Vector2 renderPosCentered, ref Color clr)
		{
			return DZ(amp, time, renderPosCentered, ref clr);
		}


		private float DZ(float amp, float time, Vector2 renderPosCentered, ref Color clr)
		{
			float renderPosCenteredLen = renderPosCentered.Length();
            float d = 0;

			double radians = RadiansInAll;

			if (radians != 0)
			{
				//float cpLen = new Vector2(CenterPosition0.X, CenterPosition0.Y).Length();
				float rMinLen = (float)(- lengthOfEffect);
				float rMaxLen = rho;// (float)rhoLen;

				bool inBounds = (renderPosCenteredLen >= rMinLen && renderPosCenteredLen <= rMaxLen);// (Rend.Length - rhoLen) > lengthOfEffect;
				//if (inBounds)
				float rLen = (rho - renderPosCenteredLen);
				float n = 1;

				//if (mWaveType == WaveEquationType.Square)	//should remove
				//{
				//    n = 1;
				//    d = (float)(amp * n);
				//}
				//else if (mWaveType == WaveEquationType.SawTooth)  //should remove
				//{
				//    n = SimpleDecay(rLen, 1, 0, lengthOfEffect);
				//    d = (float)(amp * n);
				//}
				if (mWaveType == WaveEquationType.NormalCurve)
				{

					float t = (float)(rLen / scaleN );
					float u = 0;// (float)(lengthOfEffect / scaleN);
					n = GetModifiedNormal(t, u, normalVariance);
					d = (float)(scaleAmp * amp * n);
				}
				else if (mWaveType == WaveEquationType.Sin)
				{
					n = sin(rLen / FixedWaveCount * frequency);
					d = (float)(amp * n);
				}
				else if (mWaveType == WaveEquationType.Cos)
				{
					n = cos(rLen / FixedWaveCount * frequency);
					d = (float)(amp * n);
				}
				
			} else
				d = amp;

            return d;
		}

		protected float frequency = .1f;
		[PGBrowsable]
		public float Frequency
		{
			get { return frequency; }
			set { frequency = value; }
		}

		protected bool mTimerEnabled = false;
		[PGBrowsable]
		public bool TimerEnabled
		{
			get { return mTimerEnabled; }
			set { mTimerEnabled = value; }
		}

		private double RadiansInAll
		{
			get
			{
				double twoPi = 2d * Math.PI;
				double RadiansPerWave = lengthOfEffect / FixedWaveCount * twoPi;
				//double RadiansPerRendDist = twoPi * mFixedWaveCount / rho;
				//double radiansInRendDist = rendDist * RadiansPerRendDist;
				double radians = RadiansPerWave * FixedWaveCount;
				return radians;
			}
		}

		//Parameter μ is called the mean, and it determines the location of the peak of the density function (http://en.wikipedia.org/wiki/Normal_distribution)
		private float GetModifiedNormal(float t, float u, float variance)
		{
			float tmu = t - u;	 
			float varsq = variance * variance;
			float exponent = -tmu * tmu / (2 * varsq);
			float sqrtf = 2f * (float)Math.PI;
			float n = (float)(Math.Exp(exponent) * (1 / Math.Sqrt(sqrtf * varsq))); 
			return n;
		}

		[PGBrowsable]
		public enum WaveEquationType
		{
			WaterEffect,
			NormalCurve,
			Sin,
			Cos,
			//Square,
			//SawTooth
		}

	}



}
